#ifndef GA_H_
#define GA_H_

#include "Chromosome.h"

#include <cmath>
#include <algorithm>
#include <ctime>

template <typename T>
class GA
{
public:
	GA(unsigned int pop_size,
	   unsigned int num_genes,
	   unsigned int num_pieces,
	   T lower_bound,
	   T upper_bound,
	   double (*pfit_func) (Chromosome<T> &c))
	{
		Chromosome<T> *temp;
		this->pfit_func = pfit_func;
		
		srand(time(NULL));
		
		if (lower_bound > upper_bound)
		{
			T temp = upper_bound;
			upper_bound = lower_bound;
			lower_bound = temp;
		}
		
		for (unsigned int i = 0; i < pop_size; i++)
		{
			temp = new Chromosome<T>(num_genes, num_pieces);
			temp->randomise_all_genes(lower_bound, upper_bound);
			pop.push_back(temp);
		}
	}
	
	~GA()
	{
//		for (typename std::vector<Chromosome<T>* >::iterator i = pop.begin(); i != pop.end(); i++)
//		{
//			delete *i;
//		}
	}
	
	void randomise_pop(T lower_bound, T upper_bound)
	{
		for (unsigned int i = 0; i < pop.size(); i++)
		{
			pop[i]->randomise_all_genes(lower_bound, upper_bound);
		}	
	}
	
	Chromosome<T> &operator[] (unsigned int offset)
	{
		return *(pop[offset]);
	}
	
	const Chromosome<T> &operator[] (unsigned int offset) const
	{
		return *(pop[offset]);
	}
	
	void parse_pop()
	{
		for (typename std::vector<Chromosome<T>* >::iterator i = pop.begin(); i != pop.end(); i++)
		{
			(*i)->set_fitness(std::abs(pfit_func(**i)));
		}
		
		std::sort(pop.begin(), pop.end(), Chromosome<T>::cmp);
	}
	
	void breed_pop(double fraction_breeding)
	{
		if (fraction_breeding > 1)
			fraction_breeding = 1;
		
		std::vector<Chromosome<T>* > elite;
		unsigned int M = 0, F = 0, temp_pop_size = pop.size(), temp_num_genes = pop[0]->get_num_genes(),
					 temp_num_pieces = pop[0]->get_num_pieces(),
					 num_breeders = (int)(temp_pop_size * fraction_breeding);

		Chromosome<T> *temp, *current;
		
		for (unsigned int i = 0; i < num_breeders; i++)
		{
			temp = new Chromosome<T>(temp_num_genes, temp_num_pieces);
			*temp = *(pop[i]);
			elite.push_back(temp);
		}
		
		kill_pop();
		
		for (unsigned int i = 0; i < temp_pop_size; i++)
		{
			M = (int)(((double)rand() / RAND_MAX) * num_breeders);
			F = (int)(((double)rand() / RAND_MAX) * num_breeders);
			
			current = new Chromosome<T>(temp_num_genes, temp_num_pieces);
			
			if (mutation_enabled == true)
			{
				for (unsigned int a = 0; a < num_breeders; a++)
				{
					if (((double)rand() / RAND_MAX) <= mutation_rate)
					{
						T rand_diff = (T)((((double)rand() / RAND_MAX) * (mutation_scale * 2) - mutation_scale));
						unsigned int rand_index = (unsigned int)(((double)rand() / RAND_MAX) * (elite[0]->get_num_genes()));
						unsigned int rand_piece = (unsigned int)(((double)rand() / RAND_MAX) * (elite[0]->get_num_pieces()));
						(*(elite[a]))[rand_index][rand_piece] += rand_diff;
					}
				}
			}
			
			*current = *(elite[M]) ^ *(elite[F]);
			pop.push_back(current);
		}
	}
	
	void kill_pop()
	{
		for (typename std::vector<Chromosome<T>* >::iterator i = pop.begin(); i != pop.end(); i++)
		{
			delete *i;
		}

		pop.clear();
	}
	
	void enable_mutation(double mutation_rate = 0.1f, T mutation_scale = 3)
	{
		this->mutation_enabled = true;
		this->mutation_rate = mutation_rate;
		this->mutation_scale = mutation_scale;
	}
	
	void disable_mutation()
	{
		this->mutation_enabled = false;
	}
	
	unsigned int get_pop_size() const
	{
		return pop.size();
	}
	
private:
	double (*pfit_func) (Chromosome<T> &c);
	std::vector<Chromosome<T>* > pop;
	bool mutation_enabled;
	double mutation_rate;
	T mutation_scale;
};

#endif /*GA_H_*/
